﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace PDX
{
    /// <summary>
    /// A control that binds the DataContext of its content to the
    /// <see cref="PropertyModelProperty"/>.
    /// </summary>
    public class Context : ContentControl
    {
        static Context()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(Context), new FrameworkPropertyMetadata(typeof(Context)));
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Context"/> class.
        /// </summary>
        public Context()
        {
            IsTabStop = false;

            this.Loaded += new RoutedEventHandler(ContextControl_Loaded);
            this.DataContextChanged += new DependencyPropertyChangedEventHandler(Context_DataContextChanged);
        }

        void Context_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            UpdatePropertyModel();
        }

        bool loadedFirstTime = true;
        void ContextControl_Loaded(object sender, RoutedEventArgs e)
        {
            // This is a trick to provide a better VS2010 Designer experience.
            // When running, there's no need to handle Loaded at all, OnContentChanged is sufficient.
            if (loadedFirstTime && System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
            {
                UpdateBinding();
                UpdatePropertyModel();
            }

            loadedFirstTime = false;
        }

        /// <summary>
        /// Called when the <see cref="P:System.Windows.Controls.ContentControl.Content"/> property changes.
        /// </summary>
        /// <param name="oldContent">The old value of the <see cref="P:System.Windows.Controls.ContentControl.Content"/> property.</param>
        /// <param name="newContent">The new value of the <see cref="P:System.Windows.Controls.ContentControl.Content"/> property.</param>
        protected override void OnContentChanged(object oldContent, object newContent)
        {
            base.OnContentChanged(oldContent, newContent);

            UpdateBinding();
        }

        void UpdateBinding()
        {
            var child = this.Content as FrameworkElement;
            if (child != null)
            {
                // The PropertyModel attached property is assumed to be
                // set locally or higher up in the tree.
                // Since this property inherits, our content will have PropertyModel set on itself.
                // Bind the content's DataContext to the PropertyModel.
                var binding = new Binding();
                binding.RelativeSource = new RelativeSource(RelativeSourceMode.Self);
                binding.Path = new PropertyPath(PropertyModelProperty);
                child.SetBinding(FrameworkElement.DataContextProperty, binding);
            }
        }



        /// <summary>
        /// Gets or sets the property path. Setting the property path causes
        /// an <see cref="IPropertyModel"/> to be created and set to the
        /// <see cref="PropertyModelProperty"/>.
        /// </summary>
        /// <value>
        /// The property path.
        /// </value>
        public string PropertyPath
        {
            get { return (string)GetValue(PropertyPathProperty); }
            set { SetValue(PropertyPathProperty, value); }
        }

        /// <summary>
        /// DependencyProperty backing for <see cref="PropertyPath"/>.
        /// </summary>
        public static readonly DependencyProperty PropertyPathProperty =
            DependencyProperty.Register("PropertyPath", typeof(string), typeof(Context),
            new UIPropertyMetadata(null, new PropertyChangedCallback(PropertyPathChanged)));
            

        static void PropertyPathChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            var element = sender as Context;
            if (element == null)
            {
                return;
            }

            element.UpdatePropertyModel();
        }

        /// <summary>
        /// Updates the property model.
        /// </summary>
        void UpdatePropertyModel()
        {
            if (string.IsNullOrEmpty(PropertyPath))
            {
                return;
            }

            // Determine the object to resolve the property on.
            // We respect the OriginalDataContext if it is set,
            // otherwise we use this.DataContext.
            var dc = GetOriginalDataContext(this);
            if (dc == null)
            {
                SetOriginalDataContext(this, DataContext);
                dc = DataContext;
            }

            if (dc == null)
            {
                return;
            }

            // get PropertyModel factory
            var factory = GetValue(PropertyModelFactoryProperty) as IPropertyModelFactory;
            if (factory == null)
            {
                factory = DefaultPropertyModelFactory.DefaultInstance;
            }

            // get PropertyResolver and Resolution
            var resolver = DefaultPropertyResolver.DefaultInstance;
            var resolution = resolver.Resolve(dc, PropertyPath);

            // create the PropertyModel and set it locally
            var propertyModel = factory.CreatePropertyModel(resolution);
            SetValue(PropertyModelProperty, propertyModel);
        }




        /// <summary>
        /// Gets the property model.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns></returns>
        public static object GetPropertyModel(DependencyObject obj)
        {
            return (object)obj.GetValue(PropertyModelProperty);
        }

        /// <summary>
        /// Sets the property model.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <param name="value">The value.</param>
        public static void SetPropertyModel(DependencyObject obj, object value)
        {
            obj.SetValue(PropertyModelProperty, value);
        }

        /// <summary>
        /// The PropertyModel (see <see cref="IPropertyModel"/>).
        /// The Content of this instance will be have its DataContext bound to this property.
        /// </summary>
        /// <remarks>
        /// This property is an attached inheriting DependencyProperty, meaning
        /// you could set it anywhere higher up on the tree (and not set the
        /// <see cref="PropertyPath"/> property on the <see cref="Context"/>).
        /// 
        /// Note that the type is actually object rather than <see cref="IPropertyModel"/>.
        /// I'm not sure if this would ever be desired and should consider the implications
        /// of changing it. If it is set to something other than an
        /// <see cref="IPropertyModel"/>, it is likely that many components won't work.
        /// </remarks>
        public static readonly DependencyProperty PropertyModelProperty =
            DependencyProperty.RegisterAttached("PropertyModel", typeof(object), typeof(Context),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits));





        /// <summary>
        /// Gets the property model factory.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns></returns>
        public static IPropertyModelFactory GetPropertyModelFactory(DependencyObject obj)
        {
            return (IPropertyModelFactory)obj.GetValue(PropertyModelFactoryProperty);
        }

        /// <summary>
        /// Sets the property model factory.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <param name="value">The value.</param>
        public static void SetPropertyModelFactory(DependencyObject obj, IPropertyModelFactory value)
        {
            obj.SetValue(PropertyModelFactoryProperty, value);
        }

        /// <summary>
        /// The <see cref="IPropertyModelFactory"/> to use when creating the
        /// PropertyModel (when <see cref="PropertyPath"/> is set).
        /// </summary>
        public static readonly DependencyProperty PropertyModelFactoryProperty =
            DependencyProperty.RegisterAttached("PropertyModelFactory", typeof(IPropertyModelFactory), typeof(Context),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits));





        /// <summary>
        /// Gets the original data context.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns></returns>
        public static object GetOriginalDataContext(DependencyObject obj)
        {
            return (object)obj.GetValue(OriginalDataContextProperty);
        }

        /// <summary>
        /// Sets the original data context.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <param name="value">The value.</param>
        public static void SetOriginalDataContext(DependencyObject obj, object value)
        {
            obj.SetValue(OriginalDataContextProperty, value);
        }

        /// <summary>
        /// If this property is set, its value will be used as the target when resolving
        /// <see cref="PropertyPath"/> (rather than this.DataContext).
        /// </summary>
        /// <remarks>
        /// This is used to support, for example, the <see cref="DataForm"/>.
        /// Because it uses an ItemsControl bound to a collection of <see cref="Declaration"/>s,
        /// each <see cref="Context"/> within a single item needs a clean way to get
        /// back to the viewmodel.
        /// </remarks>
        public static readonly DependencyProperty OriginalDataContextProperty =
            DependencyProperty.RegisterAttached("OriginalDataContext", typeof(object), typeof(Context),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits));







        /// <summary>
        /// Gets the declaration.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns></returns>
        public static Declaration GetDeclaration(DependencyObject obj)
        {
            return (Declaration)obj.GetValue(DeclarationProperty);
        }

        /// <summary>
        /// Sets the declaration.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <param name="value">The value.</param>
        public static void SetDeclaration(DependencyObject obj, Declaration value)
        {
            obj.SetValue(DeclarationProperty, value);
        }

        /// <summary>
        /// The <see cref="Declaration"/> that was used as the source of this
        /// <see cref="Context"/>.
        /// </summary>
        /// <remarks>
        /// Like the <see cref="OriginalDataContextProperty"/>, this is used as
        /// a way to maintain information as the data context is changed. A
        /// <see cref="Declaration"/> can define, for example, custom templates,
        /// so we pass them down via this attached inheriting property for binding purposes.
        /// </remarks>
        public static readonly DependencyProperty DeclarationProperty =
            DependencyProperty.RegisterAttached("Declaration", typeof(Declaration), typeof(Context),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits));

        

        
    }
}
